 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.update.internal.operations;

 import java.util.ArrayList ;
 import java.util.Set ;

 import org.eclipse.core.runtime.*;
 import org.eclipse.update.configuration.*;
 import org.eclipse.update.core.*;
 import org.eclipse.update.internal.core.*;

 /**
  * This class is used to construct a joint feature hiearchy.
  * Old feature reference represents feature that is
  * found on in the current configuration. New feature
  * reference is found in the feature that is an install/update
  * candidate. The element is used to join nodes of the
  * hiearchy formed by including features so that
  * each node in the hiearchy contains references to the
  * old and the new feature. Old and new features have
  * the same IDs but different versions, except in
  * the case of optional features, where the tree may
  * be constructed to bring in an optional feature
  * that was not installed initially. In that case,
  * some nodes may have old an new references with the
  * same ID and version.
  * <p>
  * Old feature reference may be null. That means
  * that the older feature with the same ID but lower
  * version was not found in the current configuration.
  */
 public class FeatureHierarchyElement {

     private Object root;
     private ArrayList children;
     private IFeatureReference oldFeatureRef;
     private IFeatureReference newFeatureRef;
     private boolean checked;
     private boolean optionalChildren;
     private boolean nativeUpgrade = false;

     public FeatureHierarchyElement(
         IFeatureReference oldRef,
         IFeatureReference newRef) {
         oldFeatureRef = oldRef;
         newFeatureRef = newRef;
     }

     public void setRoot(Object root) {
         this.root = root;
     }

     public Object getRoot() {
         return root;
     }

     /*
      * Return true if element can be checked, false otherwise.
      */
     public boolean isEditable() {
         // cannot uncheck non-optional features
 if (isOptional() == false)
             return false;
         // cannot uncheck optional feature that
 // has already been installed
 if (oldFeatureRef != null)
             return false;
         return true;
     }

     /**
      * A hirearchy node represents a 'false update' if
      * both old and new references exist and both
      * point to the feature with the same ID and version.
      * These nodes will not any bytes to be downloaded -
      * they simply exist to allow the hirarchy to
      * reach the optional children that are missing
      * and will be installed.
      */

     public boolean isFalseUpdate() {
         if (oldFeatureRef != null && newFeatureRef != null) {
             try {
                 return oldFeatureRef.getVersionedIdentifier().equals(
                     newFeatureRef.getVersionedIdentifier());
             } catch (CoreException e) {
             }
         }
         return false;
     }
     /**
      * Returns true if feature is included as optional.
      */
     public boolean isOptional() {
         return newFeatureRef instanceof IIncludedFeatureReference
             && ((IIncludedFeatureReference) newFeatureRef).isOptional();
     }
     /**
      * Returns true if this optional feature is selected
      * for installation. Non-optional features or non-editable
      * features are always checked.
      */
     public boolean isChecked() {
         return checked;
     }

     void setNativeUpgrade(boolean nativeUpgrade) {
         this.nativeUpgrade = nativeUpgrade;
     }

     /**
      * Returns true if this optional feature should
      * be enabled when installed. By default, all
      * features in the hiearchy should be enabled.
      * The exception is for optional features that
      * are updated to a new version in case where
      * the older version of the optional feature
      * is disabled in the given configuration.
      * In this case, the feature is
      * updated and disabled in order to maintain
      * its state.
      */
     public boolean isEnabled(IInstallConfiguration config) {
         if (nativeUpgrade)
             return true;
         if (isOptional() && oldFeatureRef != null) {
             try {
                 IFeature oldFeature = oldFeatureRef.getFeature(null);
                 IConfiguredSite csite =
                     UpdateUtils.getConfigSite(oldFeature, config);
                 return csite.isConfigured(oldFeature);
             } catch (CoreException e) {
             }
         }
         return true;
     }

     public IFeature getFeature() {
         try {
             IFeature feature = newFeatureRef.getFeature(null);
             return feature;
         } catch (CoreException e) {
             return null;
         }
     }

     /**
      * Selects an editable feature for installation.
      */
     public void setChecked(boolean checked) {
         this.checked = checked;
     }
     /**
      * Returns label for UI presentation.
      */
     public String getLabel() {
         try {
             return getFeatureLabel(newFeatureRef);
         } catch (CoreException e) {
             if (newFeatureRef instanceof IIncludedFeatureReference) {
                 String iname =
                     ((IIncludedFeatureReference) newFeatureRef).getName();
                 if (iname != null)
                     return iname;
             }
             try {
                 VersionedIdentifier vid =
                     newFeatureRef.getVersionedIdentifier();
                 return vid.toString();
             } catch (CoreException e2) {
             }
         }
         return null;
     }
     /**
      * Computes label from the feature.
      */
     private String getFeatureLabel(IFeatureReference featureRef)
         throws CoreException {
         IFeature feature = featureRef.getFeature(null);
         return feature.getLabel()
             + " " //$NON-NLS-1$
 + feature.getVersionedIdentifier().getVersion().toString();
     }
     /**
      * Computes children by linking matching features from the
      * old feature's and new feature's hierarchy.
      */
     public FeatureHierarchyElement[] getChildren(
         boolean update,
         boolean patch,
         IInstallConfiguration config) {
         computeChildren(update, patch, config);
         FeatureHierarchyElement[] array =
             new FeatureHierarchyElement[children.size()];
         children.toArray(array);
         return array;
     }

     public FeatureHierarchyElement[] getChildren() {
         if (children != null) {
             FeatureHierarchyElement[] array =
                 new FeatureHierarchyElement[children.size()];
             children.toArray(array);
             return array;
         }

         return new FeatureHierarchyElement[0];
     }
     /**
      * Computes children of this node.
      */
     public void computeChildren(
         boolean update,
         boolean patch,
         IInstallConfiguration config) {
         if (children == null) {
             children = new ArrayList ();
             try {
                 IFeature oldFeature = null;
                 IFeature newFeature = null;
                 newFeature = newFeatureRef.getFeature(null);
                 if (oldFeatureRef != null)
                     oldFeature = oldFeatureRef.getFeature(null);
                 optionalChildren =
                     computeElements(
                         oldFeature,
                         newFeature,
                         update,
                         patch,
                         config,
                         children);
                 for (int i = 0; i < children.size(); i++) {
                     FeatureHierarchyElement element =
                         (FeatureHierarchyElement) children.get(i);
                     element.setRoot(getRoot());
                 }
             } catch (CoreException e) {
             }
         }
     }
     /**
      *
      */
     public boolean hasOptionalChildren() {
         return optionalChildren;
     }
     /**
      * Adds checked optional features to the provided set.
      */
     public void addCheckedOptionalFeatures(
         boolean update,
         boolean patch,
         IInstallConfiguration config,
         Set set) {
         if (isOptional() && isChecked()) {
             // Do not add checked optional features
 // if this is an update case but
 // the node is not a 'true' update
 // (old and new feature are the equal)
 if (!update || !isFalseUpdate())
                 set.add(newFeatureRef);
         }
         FeatureHierarchyElement[] elements = getChildren(update, patch, config);
         for (int i = 0; i < elements.length; i++) {
             elements[i].addCheckedOptionalFeatures(update, patch, config, set);
         }
     }

     /**
      * Computes first-level children of the linked hierarchy
      * for the provided old and new features (same ID, different version
      * where new version is greater or equal the old version).
      * Old feature may be null.
      */
     public static boolean computeElements(
         IFeature oldFeature,
         IFeature newFeature,
         boolean update,
         boolean patch,
         IInstallConfiguration config,
         ArrayList list) {
         Object [] oldChildren = null;
         Object [] newChildren = getIncludedFeatures(newFeature);
         boolean optionalChildren = false;

         try {
             if (oldFeature != null) {
                 oldChildren = getIncludedFeatures(oldFeature);
             }
             for (int i = 0; i < newChildren.length; i++) {
                 IFeatureReference oldRef = null;
                 IFeatureReference newRef = (IFeatureReference) newChildren[i];
                 if (oldChildren != null) {
                     String newId =
                         newRef.getVersionedIdentifier().getIdentifier();

                     for (int j = 0; j < oldChildren.length; j++) {
                         IFeatureReference cref =
                             (IFeatureReference) oldChildren[j];
                         try {
                             if (cref
                                 .getVersionedIdentifier()
                                 .getIdentifier()
                                 .equals(newId)) {
                                 oldRef = cref;
                                 break;
                             }
                         } catch (CoreException ex) {
                         }
                     }
                 } else if (patch) {
                     // 30849 - find the old reference in the
 // configuration.
 if (!UpdateUtils.isPatch(newFeature)) {
                         oldRef = findPatchedReference(newRef, config);
                     }
                 }
                 // test if the old optional feature exists
 if (oldRef != null
                     && ((oldRef instanceof IIncludedFeatureReference
                         && ((IIncludedFeatureReference) oldRef).isOptional())
                         || patch)) {
                     try {
                         IFeature f = oldRef.getFeature(null);
                         if (f == null)
                             oldRef = null;
                     } catch (CoreException e) {
                         // missing
 oldRef = null;
                     }
                 }
                 FeatureHierarchyElement element =
                     new FeatureHierarchyElement(oldRef, newRef);
                 // If this is an update (old feature exists),
 // only check the new optional feature if the old exists.
 // Otherwise, always check.
 if (element.isOptional() && (update || patch)) {
                     element.setChecked(oldRef != null);
                     if (oldRef == null) {
                         // Does not have an old reference,
 // but it may contain an older
 // feature that may still qualify
 // for update. For example,
 // an older version may have been
 // installed natively from the CD-ROM.
 if (hasOlderVersion(newRef)) {
                             element.setNativeUpgrade(true);
                             element.setChecked(true);
                         }
                     }
                 } else
                     element.setChecked(true);
                 list.add(element);
                 element.computeChildren(update, patch, config);
                 if (element.isOptional() || element.hasOptionalChildren())
                     optionalChildren = true;
             }
         } catch (CoreException e) {
         }
         return optionalChildren;
     }
     public static boolean hasOlderVersion(IFeatureReference newRef) {
         try {
             VersionedIdentifier vid = newRef.getVersionedIdentifier();
             PluginVersionIdentifier version = vid.getVersion();
             String mode = getUpdateVersionsMode();

             IFeature[] allInstalled =
                 UpdateUtils.getInstalledFeatures(vid, false);
             for (int i = 0; i < allInstalled.length; i++) {
                 IFeature candidate = allInstalled[i];
                 PluginVersionIdentifier cversion =
                     candidate.getVersionedIdentifier().getVersion();
                 // Verify that the difference qualifies as
 // an update.
 if (mode.equals(UpdateCore.EQUIVALENT_VALUE)) {
                     if (version.isEquivalentTo(cversion))
                         return true;
                 } else if (mode.equals(UpdateCore.COMPATIBLE_VALUE)) {
                     if (version.isCompatibleWith(cversion))
                         return true;
                 }
             }
         } catch (CoreException e) {
         }
         return false;
     }

     private static IFeatureReference findPatchedReference(
         IFeatureReference newRef,
         IInstallConfiguration config)
         throws CoreException {
         VersionedIdentifier vid = newRef.getVersionedIdentifier();
         IConfiguredSite[] csites = config.getConfiguredSites();
         for (int i = 0; i < csites.length; i++) {
             IConfiguredSite csite = csites[i];
             IFeatureReference[] refs = csite.getConfiguredFeatures();
             for (int j = 0; j < refs.length; j++) {
                 IFeatureReference ref = refs[j];
                 VersionedIdentifier refVid = ref.getVersionedIdentifier();
                 if (vid.getIdentifier().equals(refVid.getIdentifier()))
                     return ref;
             }
         }
         return null;
     }

     /**
      * Returns included feature references for the given reference.
      */
     public static Object [] getIncludedFeatures(IFeatureReference ref) {
         try {
             IFeature feature = ref.getFeature(null);
             return getIncludedFeatures(feature);
         } catch (CoreException e) {
         }
         return new Object [0];
     }

     /**
      * Returns included feature references for the given feature.
      */

     public static Object [] getIncludedFeatures(IFeature feature) {
         try {
             return feature.getIncludedFeatureReferences();
         } catch (CoreException e) {
         }
         return new Object [0];
     }

     private static String getUpdateVersionsMode() {
         Preferences store = UpdateCore.getPlugin().getPluginPreferences();
         return store.getString(UpdateCore.P_UPDATE_VERSIONS);
     }
 }

